package com.wrx.service.mndata;

import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.wrx.dto.mndata.mnrawdata.MnRawDataDTO;
import com.wrx.dto.mndata.mnrawdata.MnRawDataResp;
import com.wrx.dto.mndata.mnrawdata.MnRawDataVO;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import net.sourceforge.pinyin4j.PinyinHelper;
import org.springframework.stereotype.Service;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
import java.text.DecimalFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

import static com.wrx.constants.mndata.MnDataConstants.TABLE_POINT_CODE.INSERT_HEADER;
import static com.wrx.constants.mndata.MnDataConstants.TABLE_POINT_CODE.VALUE_TEMPLATE;
import static com.wrx.constants.mndata.MnDataConstants.TABLE_RAW_DATA.INSERT_HEADER_NO_ID;
import static com.wrx.constants.mndata.MnDataConstants.TABLE_RAW_DATA.INSERT_HEADER_WITH_ID;
import static com.wrx.constants.mndata.MnDataConstants.TABLE_RAW_DATA.VALUE_NO_ID;
import static com.wrx.constants.mndata.MnDataConstants.TABLE_RAW_DATA.VALUE_WITH_ID;

@Slf4j
@Service
public class MnDataServiceImpl implements MnDataService {


    /**
     * 输入参数
     * 1. 租号id
     * 2. 数据时间范围
     * 3. 数组的pointcode
     * 4. 数据库和表名可能需要
     * 运行逻辑
     * 1. 解析点位构造JSON数据
     * 2. 解析时间范围生成rawdatasql
     * 3. 解析code生成点位sql
     * 4. 生成插入sql文件这是半自动
     * 5. 全自动可选
     */
    @SneakyThrows
    public MnRawDataResp mnRawData(MnRawDataVO vo) {
        log.info("开始生成采集数据SQL，参数：{}", vo);
        MnRawDataResp mnRawDataResp = new MnRawDataResp();
        
        // 1. 解析点位名称并构建点位数据模板
        // 将逗号分隔的点位名称字符串转换为列表
        List<String> pointNames = Arrays.stream(vo.getPointCodeNameList().split(",")).toList();
        Map<String, JSONObject> pointDataMap = buildPointDataMap(pointNames, vo.getTenantName());

        // 2. 根据时间范围生成每小时的模拟数据列表
        List<MnRawDataDTO> mnRawDataList = generateTimeRangeData(
            vo.getCollectTimeStart(),
            vo.getCollectTimeEnd(),
            vo.getStartId(),
            pointDataMap
        );
        
        // 3. 生成完整的SQL语句（包含点位表SQL和采集数据表SQL）
        String sql = generateCompleteSql(vo, mnRawDataList, pointDataMap);
        
        // 4. 设置返回结果中的点位代码集合（无论何种模式都需要返回）
        String pointCodes = String.join(",", pointDataMap.keySet());
        mnRawDataResp.setResultPointCode(pointCodes);

        // 5. 根据模拟类型执行相应处理逻辑（默认生成SQL）
        String simulateType = (vo.getSimulateType() != null && !vo.getSimulateType().isEmpty()) 
                ? vo.getSimulateType() : "GENERATE_SQL";
        handleSimulateResult(simulateType, vo, sql, mnRawDataList, mnRawDataResp);
        
        return mnRawDataResp;
    }

    /**
     * 生成完整的SQL语句（合并点位SQL和数据SQL）
     */
    private String generateCompleteSql(MnRawDataVO vo, List<MnRawDataDTO> dataList, Map<String, JSONObject> pointDataMap) {
        Integer tenantId = Integer.parseInt(vo.getTenantId());
        
        // 生成点位表插入SQL
        String pointTableName = (vo.getPointName() != null && !vo.getPointName().isEmpty()) 
                ? vo.getPointName() : "zhsc_scada_point";
        StringBuilder pointSql = convertPointCodeInsertSql(
                pointTableName, vo.getDatabaseName(), tenantId, pointDataMap);

        // 生成采集数据表插入SQL
        boolean includeId = vo.getStartId() != null;
        StringBuilder dataSql = convertRawDataSqlInsertSql(
                vo.getTableName(), vo.getDatabaseName(), tenantId, dataList, includeId);

        // 合并SQL并添加注释
        return new StringBuilder()
                .append(pointSql).append("\r\n")
                .append(dataSql)
                .append("# 点位代码集合: ").append(String.join(",", pointDataMap.keySet())).append("\r\n")
                .toString();
    }

    /**
     * 根据模拟类型处理结果
     */
    @SneakyThrows
    private void handleSimulateResult(String type, MnRawDataVO vo, String sql, 
                                    List<MnRawDataDTO> dataList, MnRawDataResp resp) {
        switch (type) {
            case "INSERT_DB":
                // 插入数据库模式：直接执行SQL
                resp.setResultSql(sql);
                executeAutoSql(vo, sql);
                log.info("插入数据库模式：成功执行 {} 条数据", dataList.size());
                break;
                
            case "GENERATE_JSON":
                // 生成JSON模式：返回JSON格式数据
                String jsonResult = generateJsonResult(dataList);
                resp.setResultJson(jsonResult);
                log.info("生成JSON模式：成功生成 {} 条数据", dataList.size());
                break;
                
            case "GENERATE_SQL":
            default:
                // 生成SQL模式：返回SQL字符串
                resp.setResultSql(sql);
                log.info("生成SQL模式：成功生成 {} 条数据", dataList.size());
                break;
        }
    }

    /**
     * 将数据列表转换为JSON格式字符串
     * 每个JSON对象占一行，方便查看
     */
    private String generateJsonResult(List<MnRawDataDTO> dataList) {
        return dataList.stream().map(dto -> {
            JSONObject jsonObj = new JSONObject();
            if (dto.getId() != null) {
                jsonObj.set("id", dto.getId());
            }
            jsonObj.set("collectTime", dto.getCollectTime());
            
            // 解析rawData字符串为JSON对象，避免嵌套转义
            if (dto.getRawData() != null && !dto.getRawData().isEmpty()) {
                jsonObj.set("rawData", JSONUtil.parseObj(dto.getRawData()));
            } else {
                jsonObj.set("rawData", null);
            }
            return JSONUtil.toJsonStr(jsonObj);
        }).collect(Collectors.joining("\r\n"));
    }

    /**
     * 构建点位数据映射
     * @param pointCodeNameList 点位名称列表
     * @param tenantName 租户名称
     * @return 点位代码 -> JSON对象的映射
     */
    private Map<String, JSONObject> buildPointDataMap(List<String> pointCodeNameList, String tenantName) {
        // 生成租户名称前缀（转成首拼）
        String tenantPrefix = (tenantName != null && !tenantName.trim().isEmpty())
                ? convertChineseToPinyin(tenantName.trim()) + "_"
                : "";

        // 使用Stream API构建点位数据映射
        return pointCodeNameList.stream()
                .map(pointName -> {
                    // 生成点位代码：租户名称首拼_点位名称的首拼
                    String pointCode = tenantPrefix + convertChineseToPinyin(pointName);
                    // 构建JSON对象
                    JSONObject pointJson = new JSONObject();
                    pointJson.set("value", "0.0");  // 默认值，后续会随机生成
                    pointJson.set("isWrite", "0");
                    pointJson.set("pointCode", pointCode);
                    pointJson.set("pointName", pointName);
                    pointJson.set("pointUnit", matchPointUnit(pointName));
                    return new AbstractMap.SimpleEntry<>(pointCode, pointJson);
                })
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        Map.Entry::getValue,
                        (existing, replacement) -> existing,  // 如果key重复，保留第一个
                        LinkedHashMap::new  // 保持插入顺序
                ));
    }

    /**
     * 中文转拼音首字母
     */
    private String convertChineseToPinyin(String chinese) {
        StringBuilder pinyin = new StringBuilder();
        char[] chars = chinese.toCharArray();

        for (char ch : chars) {
            String[] pinyins = PinyinHelper.toHanyuPinyinStringArray(ch);
            if (pinyins != null && pinyins.length > 0) {
                pinyin.append(Character.toUpperCase(pinyins[0].charAt(0)));
            } else {
                // 非中文字符直接添加
                pinyin.append(ch);
            }
        }

        return pinyin.toString();
    }

    /**
     * 匹配点位单位
     */
    private String matchPointUnit(String pointName) {
        // 使用 GenerateService 中的 POINT_UNIT_MAPPING 逻辑
        Map<List<String>, String> unitMapping = new LinkedHashMap<>();
        unitMapping.put(Arrays.asList("压力"), "MPa");
        unitMapping.put(Arrays.asList("电流"), "A");
        unitMapping.put(Arrays.asList("温度"), "℃");
        unitMapping.put(Arrays.asList("频率"), "Hz");
        unitMapping.put(Arrays.asList("功率"), "kW");
        unitMapping.put(Arrays.asList("能耗", "电能", "电量"), "kW·h");
        unitMapping.put(Arrays.asList("浊度"), "NTU");
        unitMapping.put(Arrays.asList("瞬时流量", "流量"), "m³/h");
        unitMapping.put(Arrays.asList("累计流量", "水量"), "m³");
        unitMapping.put(Arrays.asList("余氯"), "mg/L");
        unitMapping.put(Arrays.asList("速度", "液位"), "m");
        unitMapping.put(Arrays.asList("速度给定"), "L/h");
        unitMapping.put(Arrays.asList("流速"), "m/s");
        unitMapping.put(Arrays.asList("电导率"), "μS/cm");
        unitMapping.put(Arrays.asList("电压"), "V");

        for (Map.Entry<List<String>, String> entry : unitMapping.entrySet()) {
            for (String keyword : entry.getKey()) {
                if (pointName.contains(keyword)) {
                    return entry.getValue();
                }
            }
        }

        return ""; // 默认无单位
    }

    /**
     * 根据时间范围生成每小时的数据
     * @param startDateStr 开始日期 yyyy-MM-dd
     * @param endDateStr 结束日期 yyyy-MM-dd
     * @param pointDataMap 点位数据模板
     * @return 数据列表
     */
    /**
     * 生成指定时间范围内的模拟数据
     * 
     * @param startDateStr 开始日期（格式：yyyy-MM-dd）
     * @param endDateStr 结束日期（格式：yyyy-MM-dd）
     * @param startId 起始ID（如果为null则不设置ID，使用数据库自增）
     * @param pointDataMap 点位数据映射（key: pointCode, value: 点位模板数据）
     * @return 生成的数据列表，每天24条（每小时一条）
     */
    private List<MnRawDataDTO> generateTimeRangeData(
            String startDateStr,
            String endDateStr,
            Integer startId,
            Map<String, JSONObject> pointDataMap) {

        // 初始化日期时间格式化器
        DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:00:00");
        LocalDate startDate = LocalDate.parse(startDateStr, dateFormatter);
        LocalDate endDate = LocalDate.parse(endDateStr, dateFormatter);

        // 初始化随机数生成器和数值格式化器
        Random random = new Random();
        DecimalFormat df = new DecimalFormat("0.00");

        // 用于自增ID的数组（使用数组是为了在lambda中修改值）
        final int[] currentId = {startId != null ? startId : 0};

        // 使用Stream API生成所有数据：遍历日期范围 -> 每天24小时 -> 生成数据
        return startDate.datesUntil(endDate.plusDays(1))  // 生成日期流（包含结束日期）
                .flatMap(date -> java.util.stream.IntStream.rangeClosed(0, 23)  // 每天0-23点
                        .mapToObj(hour -> {
                            // 构建当前小时的时间戳
                            LocalDateTime dateTime = date.atTime(hour, 0);
                            String collectTime = dateTime.format(dateTimeFormatter);
                            
                            // 为所有点位生成随机值的JSON数据
                            JSONObject rawDataJson = generatePointsData(pointDataMap, random, df);
                            
                            // 创建数据传输对象
                            MnRawDataDTO dto = new MnRawDataDTO();
                            // 如果指定了起始ID，则设置ID并自增；否则不设置（使用数据库自增）
                            if (startId != null) {
                                dto.setId(currentId[0]++);
                            }
                            dto.setCollectTime(collectTime);
                            dto.setRawData(rawDataJson.toString());
                            return dto;
                        }))
                .collect(Collectors.toList());
    }

    /**
     * 为所有点位生成随机数据
     * 
     * @param pointDataMap 点位数据映射（key: pointCode, value: 点位模板数据）
     * @param random 随机数生成器
     * @param df 数值格式化器（保留两位小数）
     * @return 包含所有点位随机值的JSON对象
     */
    private JSONObject generatePointsData(Map<String, JSONObject> pointDataMap, Random random, DecimalFormat df) {
        JSONObject rawDataJson = new JSONObject();
        
        // 遍历所有点位，为每个点位生成随机值
        pointDataMap.forEach((pointCode, pointTemplate) -> {
            // 深拷贝模板数据（避免修改原模板）
            JSONObject pointData = JSONUtil.parseObj(pointTemplate.toString());
            
            // 生成随机值（范围：0.00 到 10.00）
            pointData.set("value", df.format(random.nextDouble() * 10));
            
            // 将点位数据添加到结果JSON中
            rawDataJson.set(pointCode, pointData);
        });
        
        return rawDataJson;
    }

    /**
     * 全自动模式：执行SQL到数据库
     */
    private void executeAutoSql(MnRawDataVO vo, String sql) throws Exception {
        // 1. 数据库连接信息
        String url = String.format(
            "jdbc:mysql://%s:3306/%s?allowMultiQueries=true&useUnicode=true&useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&nullCatalogMeansCurrent=true",
            vo.getDatabaseUrl(),
            vo.getDatabaseName()
        );

        // 2. 加载驱动（JDBC 4.0+ 支持自动驱动发现，但为了兼容性保留显式加载）
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            // 如果显式加载失败，尝试使用自动发现机制（JDBC 4.0+）
            log.warn("显式加载MySQL驱动失败，尝试使用自动发现机制：{}", e.getMessage());
        }

        // 3. 连接数据库并执行SQL
        try (Connection conn = DriverManager.getConnection(url, vo.getUserName(), vo.getPassword());
             Statement stmt = conn.createStatement()) {

            log.info("成功连接到数据库：{}", vo.getDatabaseUrl());

            // 执行SQL（支持多条SQL语句）
            stmt.execute(sql);

            log.info("SQL执行成功");
        } catch (Exception e) {
            log.error("执行SQL失败", e);
            throw new RuntimeException("执行SQL失败：" + e.getMessage(), e);
        }
    }

    private StringBuilder convertRawDataSqlInsertSql(String tableName, String dataBaseName, Integer tenantId, List<MnRawDataDTO> jsonList, boolean includeId) {
        // 根据是否包含id选择不同的模板
        String insertHeader = includeId 
                ? String.format(INSERT_HEADER_WITH_ID, dataBaseName, tableName)
                : String.format(INSERT_HEADER_NO_ID, dataBaseName, tableName);
        
        // 生成所有数据值的SQL片段，使用逗号和换行符连接
        String valuesSql = jsonList.stream()
                .map(dto -> includeId
                        ? String.format(VALUE_WITH_ID,
                                dto.getId(),
                                dto.getCollectTime(),
                                dto.getRawData(),
                                tenantId)
                        : String.format(VALUE_NO_ID,
                                dto.getCollectTime(),
                                dto.getRawData(),
                                tenantId))
                .collect(Collectors.joining(",\r\n    "));

        // 构建完整的SQL语句
        return new StringBuilder()
                .append("# 采集历史数据（raw_data）sql\r\n")
                .append(insertHeader)
                .append(valuesSql)
                .append(";\r\n");
    }

    /**
     * 生成点位插入SQL
     * @param tableName 点位表名
     * @param dataBaseName 数据库名
     * @param tenantId 租户ID
     * @param pointDataMap 点位数据映射（包含pointCode, pointName, pointUnit）
     * @return SQL字符串
     */
    private StringBuilder convertPointCodeInsertSql(String tableName, String dataBaseName, Integer tenantId, Map<String, JSONObject> pointDataMap) {
        // 生成所有点位值的SQL片段，使用逗号和换行符连接
        String valuesSql = pointDataMap.entrySet().stream()
                .map(entry -> {
                    String pointCode = entry.getKey();
                    JSONObject pointJson = entry.getValue();
                    String pointUnit = pointJson.getStr("pointUnit");
                    return String.format(VALUE_TEMPLATE,
                            pointCode,
                            pointJson.getStr("pointName"),
                            pointUnit != null ? pointUnit : "",
                            pointCode,  // 用于生成 test_iot_point_id_%s
                            tenantId
                    );
                })
                .collect(Collectors.joining(",\r\n    "));

        // 构建完整的SQL语句
        return new StringBuilder()
                .append("# 点位（point）sql\r\n")
                .append(String.format(INSERT_HEADER, dataBaseName, tableName))
                .append(valuesSql)
                .append(";\r\n");
    }
}
